package com.gopay.common.id;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 基于雪花算法生成分布式ID
 *
 * 共64位 存储于long型 ：
 * 位数由高到低 ：
 *  42 位 ：毫秒级时间 = 当前时间戳 - 基准时间时间戳。 最大值 4398046511104  能用139年。
 *  5  位 ：服务号：     范围 0 - 31 ，即支持32种不同服务
 *  5  位 : 服务节点：   范围 0 - 31 ，即支持每种服务部署32个节点
 *  12 位 ：毫秒内自增序列号：取值 0 - 4095 ， 即支持毫秒级并发4096次ID获取。
 *
 *  对系统时间的依赖性非常强，需关闭ntp的时间同步功能。当检测到ntp时间调整后，将会拒绝分配id.
 */
public class SnowflakeIdGenerator implements Generator<GUID>{

    private final static Logger logger = LoggerFactory.getLogger(SnowflakeIdGenerator.class);

    /**
     * 时间起始标记点，作为基准，一般取系统的最近时间
     */
    private final long epoch = 1451577600000L; //1451577600000  基准时间：20160101

    /**
     * 节点标识
     */
    private final long workerId ;

    /**
     * 节点标识位数
     */
    private final long workerIdBits = 10L;

    /**
     * 节点ID最大值: 1023  已将节点id进行拆分成（服务 +  服务id）
     */
    private final long maxWorkerId = ~(-1L << this.workerIdBits);

    /**
     * 0，并发控制
     */
    private long sequence = 0L;

    /**
     * 毫秒内自增位
     */
    private final long sequenceBits = 12L;

    /**
     * 节点ID左移偏移量 : 12
     */
    private final long workerIdShift = this.sequenceBits;

    /**
     * 毫秒级时间戳左移偏移量 : 22
     */
    private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;

    /**
     * 序列号掩码 ：序列号循环计算时使用  最大值 4095
     */
    private final long sequenceMask = ~(-1L << this.sequenceBits);    // 52个0 + 1111 1111 1111 (12位1)

    /**
     * 上次取分布式ID的时间戳
     */
    private long lastTimestamp = -1L;

    private WorkerId worker;

    /**
     * 委托者：生成机器标识 或 进程标识 使用
     */
    private Generator delegate = null;

    private static SnowflakeIdGenerator instance = new SnowflakeIdGenerator();


    public SnowflakeIdGenerator() {
        delegate = GeneratorBuilder.buildWorkerGenUseProp();
        if(delegate == null){
            throw new NullPointerException("委托者不能为空!");
        }

        this.worker = (WorkerId)delegate.generator();
        workerId = worker.getWorkerId();

//        /**
//         * workerId最大值 1023，如果从中间件中获取的id溢出需取余: 比如 1 和 1024 的值一样。
//         * 此种情况 ： 如果获取到id=1的进程在某一进程获取到1024时，依然存活。那么将有两个进程的workerId相同。
//         */
//        this.workerId = id % (maxWorkerId + 1);

        logger.info("Snowflake init done . workerId = " + workerId);
    }

    private synchronized GUID nextId() {
        long timestamp = this.timeGen();
        if (this.lastTimestamp == timestamp) { // 如果上一个timestamp与新产生的相等，则sequence加一(0-4095循环); 对新的timestamp，sequence从0开始
            this.sequence = this.sequence + 1 & this.sequenceMask;
            if (this.sequence == 0) {
                timestamp = this.utilNextMillis(this.lastTimestamp);// 重新生成timestamp
            }
        } else {
            this.sequence = 0;
        }

        if (timestamp < this.lastTimestamp) {
            logger.error(String.format("clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp)));
            throw new RuntimeException(String.format("clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp)));
        }

        this.lastTimestamp = timestamp;

        GUID guid = new GUID(timestamp - this.epoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence);
        guid.setTimestamp(timestamp);
        guid.setSequence(sequence);
        guid.setWorkerId(workerId);
        guid.setWorker(worker);

        return guid;
    }

    /**
     * 等待下一个毫秒的到来, 保证返回的毫秒数在参数lastTimestamp之后
     */
    private long utilNextMillis(long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    /**
     * 获得系统当前毫秒数
     */
    private static long timeGen() {
        return System.currentTimeMillis();
    }

    public static SnowflakeIdGenerator instance(){
        return instance;
    }

    @Override
    public GUID generator(){
        return nextId();
    }
}
